package com.docmgmt.mvp.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.docmgmt.mvp.dto.*;
import com.docmgmt.mvp.dto.ragflow.*;
import com.docmgmt.mvp.entity.*;
import com.docmgmt.mvp.exception.BusinessException;
import com.docmgmt.mvp.exception.ForbiddenException;
import com.docmgmt.mvp.mapper.*;
import com.docmgmt.mvp.ragflow.RAGFlowClient;
import com.docmgmt.mvp.service.AiService;
import com.docmgmt.mvp.service.KnowledgeBaseService;
import com.docmgmt.mvp.service.PermissionService;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

/**
 * AI服务实现（v3.0）
 *
 * @author Dora
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class AiServiceImpl extends ServiceImpl<AiDialogMapper, AiDialogEntity> implements AiService {

    private final RAGFlowClient ragflowClient;
    private final KnowledgeBaseService knowledgeBaseService;
    private final FileMapper fileMapper;
    private final UserMapper userMapper;
    private final FolderMapper folderMapper;
    private final AiDialogMapper aiDialogMapper;
    private final ConversationMapper conversationMapper;
    private final KbFileMappingMapper kbFileMappingMapper;
    private final PermissionService permissionService;
    private final ObjectMapper objectMapper;

    /**
     * AI问答（v3.0版本：权限感知 + 4种范围）
     *
     * 4种问答范围：
     * 1. global: 全局可见文档（根据用户权限过滤）
     * 2. kb: 指定知识库
     * 3. folder: 指定文件夹（递归包含子文件夹）
     * 4. file: 指定单个文件
     *
     * @param request 问答请求
     * @param userId  用户ID
     * @return 问答响应
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public AiChatResponse chat(AiChatRequest request, Long userId) {
        log.info("AI问答 v3.1: userId={}, query={}, dialogId={}, scopeType={}, scopeId={}",
                userId, request.getQuery(), request.getDialogId(),
                request.getScope() != null ? request.getScope().getType() : "global",
                request.getScope() != null ? request.getScope().getId() : "null");

        // 参数验证
        if (request.getQuery() == null || request.getQuery().trim().isEmpty()) {
            throw new BusinessException("问题不能为空");
        }

        long startTime = System.currentTimeMillis();

        try {
            // Step 1: 获取或创建对话会话
            AiDialogEntity dialog;
            boolean isNewDialog = false;
            String scopeType = request.getScope() != null ? request.getScope().getType() : "global";
            Long scopeId = request.getScope() != null ? request.getScope().getId() : null;

            if (request.getDialogId() != null) {
                // 场景1：续接已有会话
                dialog = aiDialogMapper.selectById(request.getDialogId());
                if (dialog == null) {
                    throw new BusinessException("对话会话不存在");
                }
                if (!dialog.getUserId().equals(userId)) {
                    throw new ForbiddenException("无权访问此对话");
                }
                if ("deleted".equals(dialog.getStatus())) {
                    throw new BusinessException("对话已删除，无法继续");
                }
                log.info("续接已有会话: dialogId={}, ragflowSessionId={}",
                        dialog.getId(), dialog.getRagflowSessionId());
            } else {
                // 场景2：创建新会话
                dialog = new AiDialogEntity();
                dialog.setDialogId(UUID.randomUUID().toString());
                dialog.setUserId(userId);
                dialog.setTitle(generateDialogTitle(request.getQuery()));
                dialog.setScopeType(scopeType);
                dialog.setScopeId(scopeId);
                dialog.setKbIds(null);
                dialog.setTotalRounds(0);
                dialog.setTotalTokensUsed(0);
                dialog.setLastQueryTime(LocalDateTime.now());
                dialog.setStatus("active");
                dialog.setCreatedAt(LocalDateTime.now());
                dialog.setUpdatedAt(LocalDateTime.now());

                aiDialogMapper.insert(dialog);
                isNewDialog = true;
                log.info("创建新会话: dialogId={}, scopeType={}, scopeId={}",
                        dialog.getDialogId(), scopeType, scopeId);
            }

            // Step 2: 获取或创建 RAGFlow Session
            String ragflowSessionId = dialog.getRagflowSessionId();
            String chatId = knowledgeBaseService.getOrCreateMainChatAssistant();

            if (ragflowSessionId == null || ragflowSessionId.isEmpty()) {
                // 创建 RAGFlow Session
                String sessionName = generateSessionName(userId, dialog);

                try {
                    ragflowSessionId = ragflowClient.createSession(chatId, sessionName);

                    // 更新 ai_dialog 记录
                    dialog.setRagflowSessionId(ragflowSessionId);
                    aiDialogMapper.updateById(dialog);

                    log.info("RAGFlow Session 创建成功: sessionId={}", ragflowSessionId);
                } catch (Exception e) {
                    log.error("创建 RAGFlow Session 失败", e);

                    // 如果是新创建的会话，回滚删除
                    if (isNewDialog) {
                        aiDialogMapper.deleteById(dialog.getId());
                    }

                    throw new BusinessException("创建对话会话失败，请稍后重试: " + e.getMessage());
                }
            }

            // Step 3: 根据scope类型查询用户可见的doc_ids
            List<String> visibleDocIds = getDocIdsByScope(userId, scopeType, scopeId);

            if (visibleDocIds.isEmpty()) {
                log.warn("用户{}在scope={}下没有可见文档", userId, scopeType);
                return AiChatResponse.builder()
                    .conversationId(dialog.getDialogId())
                    .dialogId(dialog.getId())
                    .answer("当前范围内没有可查询的文档，请先上传文件或调整问答范围。")
                    .references(List.of())
                    .queriedKbIds(List.of())
                    .queriedKbNames(List.of())
                    .tokensUsed(null)
                    .responseTime((System.currentTimeMillis() - startTime) / 1000.0)
                    .build();
            }

            // Step 4: 调用RAGFlow API进行问答（传递session_id）
            String kbId = knowledgeBaseService.getOrCreateDefaultKnowledgeBase();
            log.info("使用Chat Assistant: {}, 知识库: {}, 可见文档数: {}, sessionId: {}",
                    chatId, kbId, visibleDocIds.size(), ragflowSessionId);

            RAGFlowAnswerData answerData = ragflowClient.chatWithDocFilterEnhanced(
                    chatId, request.getQuery(), visibleDocIds, ragflowSessionId);

            // Step 5: 提取P2数据
            String answer = answerData.getAnswer();
            List<RAGFlowChunk> references = ragflowClient.extractReferences(answerData);
            List<String> docIds = ragflowClient.extractDocIds(answerData);
            Integer tokensUsed = ragflowClient.extractTokenUsage(answerData);

            log.info("P2数据提取: references={}, docIds={}, tokens={}",
                    references.size(), docIds.size(), tokensUsed);

            // Step 6: 保存单轮对话记录（包含P2数据）
            int nextRound = dialog.getTotalRounds() + 1;
            ConversationEntity conversation = new ConversationEntity();
            conversation.setDialogId(dialog.getId());
            conversation.setRound(nextRound);
            conversation.setUserId(userId);
            conversation.setQuery(request.getQuery());
            conversation.setAnswer(answer);

            // P2: 序列化并保存
            try {
                conversation.setDocIds(objectMapper.writeValueAsString(docIds));
                conversation.setReferences(objectMapper.writeValueAsString(references));
            } catch (Exception e) {
                log.error("序列化P2数据失败", e);
                conversation.setDocIds("[]");
                conversation.setReferences("[]");
            }

            conversation.setTokensUsed(tokensUsed);
            conversation.setResponseTimeMs((int) (System.currentTimeMillis() - startTime));
            conversation.setFeedback("none");
            conversation.setCreatedAt(LocalDateTime.now());

            conversationMapper.insert(conversation);

            // Step 7: 更新 ai_dialog 统计信息
            dialog.setTotalRounds(nextRound);
            dialog.setTotalTokensUsed((dialog.getTotalTokensUsed() != null ? dialog.getTotalTokensUsed() : 0) +
                    (tokensUsed != null ? tokensUsed : 0));
            dialog.setLastQueryTime(LocalDateTime.now());
            dialog.setUpdatedAt(LocalDateTime.now());
            aiDialogMapper.updateById(dialog);

            // Step 8: 构造响应（包含P2数据）
            // 将RAGFlowChunk映射为DocumentReference，需要查询系统文件信息
            List<AiChatResponse.DocumentReference> documentReferences = references.stream()
                    .map(chunk -> {
                        // 根据RAGFlow的documentId查询系统FileEntity
                        FileEntity fileEntity = null;
                        if (chunk.getDocumentId() != null) {
                            LambdaQueryWrapper<FileEntity> fileQuery = new LambdaQueryWrapper<>();
                            fileQuery.eq(FileEntity::getRagflowDocId, chunk.getDocumentId());
                            fileEntity = fileMapper.selectOne(fileQuery);
                        }

                        // 构造DocumentReference
                        AiChatResponse.DocumentReference ref = new AiChatResponse.DocumentReference();
                        if (fileEntity != null) {
                            ref.setFileId(fileEntity.getId());
                            ref.setFileName(fileEntity.getFileName());
                            ref.setFolderPath(getFolderPath(fileEntity.getFolderId()));
                            // TODO: 生成download和preview URL
                            ref.setDownloadUrl("/api/files/" + fileEntity.getId() + "/download");
                            ref.setPreviewUrl("/api/files/" + fileEntity.getId() + "/preview");
                        } else {
                            // 如果找不到对应系统文件，使用RAGFlow返回的基础信息
                            ref.setFileName(chunk.getDocumentName());
                        }
                        ref.setContent(chunk.getContent());
                        ref.setSimilarity(chunk.getSimilarity());
                        ref.setPage(null); // RAGFlow chunk暂不提供页码信息
                        return ref;
                    })
                    .collect(Collectors.toList());

            // 计算响应时间
            double responseTime = (System.currentTimeMillis() - startTime) / 1000.0;

            return AiChatResponse.builder()
                    .conversationId(dialog.getDialogId())
                    .dialogId(dialog.getId())
                    .answer(answer)
                    .references(documentReferences)
                    .queriedKbIds(List.of(kbId))
                    .queriedKbNames(List.of("Company Knowledge Base"))
                    .tokensUsed(tokensUsed)
                    .responseTime(responseTime)
                    .build();

        } catch (BusinessException | ForbiddenException e) {
            throw e;
        } catch (Exception e) {
            log.error("AI问答失败", e);
            throw new BusinessException("AI问答失败: " + e.getMessage(), e);
        }
    }

    /**
     * AI问答（SSE流式响应版本）
     *
     * 适用于需要实时返回AI回答的场景，提升用户体验
     *
     * @param request 问答请求
     * @param userId  用户ID
     * @return SSE Emitter
     */
    @Override
    public SseEmitter chatStream(AiChatRequest request, Long userId) {
        log.info("AI问答流式响应: userId={}, query={}", userId, request.getQuery());

        SseEmitter emitter = new SseEmitter(60000L); // 60秒超时

        // 异步处理
        new Thread(() -> {
            try {
                // 参数验证
                if (request.getQuery() == null || request.getQuery().trim().isEmpty()) {
                    emitter.send(SseEmitter.event()
                            .name("error")
                            .data("问题不能为空"));
                    emitter.complete();
                    return;
                }

                // Step 1: 查询可见文档
                String scopeType = request.getScope() != null ? request.getScope().getType() : "global";
                Long scopeId = request.getScope() != null ? request.getScope().getId() : null;
                List<String> visibleDocIds = getDocIdsByScope(userId, scopeType, scopeId);

                if (visibleDocIds.isEmpty()) {
                    emitter.send(SseEmitter.event()
                            .name("message")
                            .data("当前范围内没有可查询的文档，请先上传文件或调整问答范围。"));
                    emitter.complete();
                    return;
                }

                // Step 2: 获取Chat Assistant（RAGFlow v0.21.1）
                String chatId = knowledgeBaseService.getOrCreateMainChatAssistant();

                emitter.send(SseEmitter.event()
                        .name("start")
                        .data(String.format("正在查询%d个文档...", visibleDocIds.size())));

                long chatStartTime = System.currentTimeMillis();

                // TODO v3.1: chatStream() 方法暂不支持多轮对话，需要传递 sessionId 参数
                // 临时方案：传递 null 作为 sessionId（不保存对话历史到 RAGFlow）
                RAGFlowAnswerData answerData = ragflowClient.chatWithDocFilterEnhanced(chatId, request.getQuery(), visibleDocIds, null);
                String answer = answerData.getAnswer();

                // 提取P2数据
                List<RAGFlowChunk> references = ragflowClient.extractReferences(answerData);
                List<String> docIds = ragflowClient.extractDocIds(answerData);
                Integer tokensUsed = ragflowClient.extractTokenUsage(answerData);

                log.info("流式P2数据提取: references={}, docIds={}, tokens={}",
                        references.size(), docIds.size(), tokensUsed);

                // 模拟流式输出（每次发送一部分）
                String[] words = answer.split("(?<=\\G.{20})"); // 每20个字符一段
                for (String word : words) {
                    emitter.send(SseEmitter.event()
                            .name("message")
                            .data(word));
                    Thread.sleep(50); // 模拟流式延迟
                }

                // 发送完成事件
                emitter.send(SseEmitter.event()
                        .name("done")
                        .data("回答完成"));

                emitter.complete();

                // TODO v3.1: chatStream() 方法暂不支持多轮对话
                // 需要重构此方法以支持 dialogId 参数和 RAGFlow Session 管理
                // 当前版本：不保存对话历史到数据库
                log.warn("chatStream() 方法暂不支持多轮对话，对话历史未保存");

            } catch (Exception e) {
                log.error("SSE流式响应失败", e);
                try {
                    // 修复：检查emitter状态，避免重复发送或已关闭的emitter
                    emitter.send(SseEmitter.event()
                            .name("error")
                            .data("AI问答失败: " + e.getMessage()));
                    emitter.completeWithError(e);
                } catch (IllegalStateException | java.io.IOException ex) {
                    // Emitter已关闭、超时或连接断开
                    log.warn("SSE Emitter已关闭或超时，无法发送错误事件: {}", ex.getMessage());
                } catch (Exception ex) {
                    log.error("发送错误事件失败", ex);
                }
            }
        }).start();

        return emitter;
    }

    /**
     * 获取用户对话历史
     *
     * @param userId 用户ID
     * @param limit  返回数量限制
     * @return 对话历史列表（按时间倒序）
     */
    @Override
    public List<AiDialogEntity> getDialogHistory(Long userId, Integer limit) {
        LambdaQueryWrapper<AiDialogEntity> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(AiDialogEntity::getUserId, userId)
               .eq(AiDialogEntity::getStatus, "active")
               .orderByDesc(AiDialogEntity::getLastQueryTime)
               .last("LIMIT " + (limit != null ? limit : 20));

        return aiDialogMapper.selectList(wrapper);
    }

    /**
     * 删除对话历史
     *
     * @param userId   用户ID
     * @param dialogId 对话ID
     */
    @Override
    @Transactional
    public void deleteDialog(Long userId, Long dialogId) {
        AiDialogEntity dialog = aiDialogMapper.selectById(dialogId);

        if (dialog == null) {
            throw new BusinessException("对话不存在");
        }

        // 权限检查：只能删除自己的对话
        if (!dialog.getUserId().equals(userId)) {
            throw new ForbiddenException("无权限删除他人对话");
        }

        // 软删除：更新状态为deleted
        dialog.setStatus("deleted");
        dialog.setUpdatedAt(LocalDateTime.now());
        aiDialogMapper.updateById(dialog);

        log.info("对话历史已删除: dialogId={}, userId={}", dialogId, userId);
    }

    /**
     * 清空用户所有对话历史
     *
     * @param userId 用户ID
     */
    @Override
    @Transactional
    public void clearAllDialogs(Long userId) {
        LambdaQueryWrapper<AiDialogEntity> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(AiDialogEntity::getUserId, userId)
               .eq(AiDialogEntity::getStatus, "active");

        List<AiDialogEntity> dialogs = aiDialogMapper.selectList(wrapper);
        for (AiDialogEntity dialog : dialogs) {
            dialog.setStatus("deleted");
            dialog.setUpdatedAt(LocalDateTime.now());
            aiDialogMapper.updateById(dialog);
        }

        log.info("已清空用户所有对话历史: userId={}, count={}", userId, dialogs.size());
    }

    // ==================== 私有辅助方法 ====================

    /**
     * 根据scope查询用户可见的RAGFlow doc_ids
     */
    private List<String> getDocIdsByScope(Long userId, String scopeType, Long scopeId) {
        UserEntity currentUser = userMapper.selectById(userId);
        if (currentUser == null) {
            throw new BusinessException("用户不存在");
        }

        List<String> docIds = new ArrayList<>();

        switch (scopeType) {
            case "global":
                // 全局范围：所有用户有权限的文档
                docIds = getDocIdsForGlobalScope(userId, currentUser);
                break;

            case "kb":
                // 知识库范围：指定知识库内的文档
                docIds = getDocIdsForKbScope(userId, scopeId);
                break;

            case "folder":
                // 文件夹范围：指定文件夹及子文件夹内的文档
                docIds = getDocIdsForFolderScope(userId, scopeId);
                break;

            case "file":
                // 文件范围：单个文件
                docIds = getDocIdsForFileScope(userId, scopeId);
                break;

            default:
                throw new BusinessException("不支持的scope类型: " + scopeType);
        }

        return docIds;
    }

    /**
     * 获取全局范围的doc_ids（v3.0隐私级别过滤）
     *
     * 缓存策略：
     * - 缓存名称：user_accessible_docs
     * - 缓存键：userId:global:deptId
     * - TTL：5分钟（在application.yml中配置）
     * - 条件：空结果不缓存
     *
     * 缓存失效场景：
     * - 文件上传/删除/移动时清理所有缓存
     * - 用户权限变更时清理该用户的缓存
     */
    @Cacheable(
        value = "user_accessible_docs",
        key = "#userId + ':global:' + (#currentUser.departmentId != null ? #currentUser.departmentId : 'null')",
        unless = "#result.isEmpty()"
    )
    protected List<String> getDocIdsForGlobalScope(Long userId, UserEntity currentUser) {
        log.info("开始查询全局范围文档: userId={}, deptId={}", userId, currentUser.getDepartmentId());

        // 使用原生 SQL 查询（修复 MyBatis-Plus LambdaQueryWrapper 问题）
        List<FileEntity> files = fileMapper.selectAccessibleDocsForGlobal(userId, currentUser.getDepartmentId());

        log.info("查询到文件数量: {}", files.size());

        List<String> docIds = files.stream()
            .map(FileEntity::getRagflowDocId)
            .filter(Objects::nonNull)
            .collect(Collectors.toList());

        log.info("过滤后的 RAGFlow doc_ids 数量: {}", docIds.size());
        if (!docIds.isEmpty()) {
            log.info("RAGFlow doc_ids: {}", String.join(",", docIds));
        }

        return docIds;
    }

    /**
     * 获取知识库范围的doc_ids（v3.0实现）
     *
     * 从kb_file_mapping表查询指定知识库下的文件，并应用权限过滤
     *
     * 缓存策略：
     * - 缓存名称：user_accessible_docs
     * - 缓存键：userId:kb:kbId
     * - TTL：5分钟
     * - 条件：空结果不缓存
     */
    @Cacheable(
        value = "user_accessible_docs",
        key = "#userId + ':kb:' + #kbId",
        unless = "#result.isEmpty()"
    )
    protected List<String> getDocIdsForKbScope(Long userId, Long kbId) {
        if (kbId == null) {
            throw new BusinessException("kb scope必须提供kbId");
        }

        // Step 1: 从kb_file_mapping表查询该知识库下的所有活跃文件ID
        LambdaQueryWrapper<KbFileMappingEntity> mappingQuery = new LambdaQueryWrapper<>();
        mappingQuery.eq(KbFileMappingEntity::getKbId, kbId)
                    .eq(KbFileMappingEntity::getFileStatus, "active");

        List<KbFileMappingEntity> mappings = kbFileMappingMapper.selectList(mappingQuery);

        if (mappings.isEmpty()) {
            log.info("知识库{}下没有文件", kbId);
            return List.of();
        }

        // Step 2: 提取file_ids
        List<Long> fileIds = mappings.stream()
            .map(KbFileMappingEntity::getFileId)
            .collect(Collectors.toList());

        // Step 3: 查询文件实体并应用权限过滤
        List<FileEntity> files = fileMapper.selectBatchIds(fileIds);

        UserEntity currentUser = userMapper.selectById(userId);
        if (currentUser == null) {
            throw new BusinessException("用户不存在");
        }

        // Step 4: 应用hasFilePermission权限过滤
        List<String> docIds = files.stream()
            .filter(file -> hasFilePermission(userId, file))
            .map(FileEntity::getRagflowDocId)
            .filter(Objects::nonNull)
            .collect(Collectors.toList());

        log.info("知识库{}范围查询: 总文件数={}, 有权限文件数={}", kbId, files.size(), docIds.size());
        return docIds;
    }

    /**
     * 获取文件夹范围的doc_ids（递归包含子文件夹）
     *
     * 缓存策略：
     * - 缓存名称：user_accessible_docs
     * - 缓存键：userId:folder:folderId
     * - TTL：5分钟
     * - 条件：空结果不缓存
     */
    @Cacheable(
        value = "user_accessible_docs",
        key = "#userId + ':folder:' + #folderId",
        unless = "#result.isEmpty()"
    )
    protected List<String> getDocIdsForFolderScope(Long userId, Long folderId) {
        if (folderId == null) {
            throw new BusinessException("folder scope必须提供folderId");
        }

        // 检查用户对该文件夹的访问权限
        if (!permissionService.checkPermission(userId, "folder:" + folderId, "read")) {
            throw new ForbiddenException("无权限访问该文件夹");
        }

        // 递归获取该文件夹及所有子文件夹的ID
        List<Long> folderIds = getFolderIdsRecursive(folderId);

        // 查询这些文件夹下的所有文件
        LambdaQueryWrapper<FileEntity> query = new LambdaQueryWrapper<>();
        query.in(FileEntity::getFolderId, folderIds)
             .isNotNull(FileEntity::getRagflowDocId);

        List<FileEntity> files = fileMapper.selectList(query);
        return files.stream()
            .map(FileEntity::getRagflowDocId)
            .filter(Objects::nonNull)
            .collect(Collectors.toList());
    }

    /**
     * 获取单个文件的doc_ids
     *
     * 缓存策略：
     * - 缓存名称：user_accessible_docs
     * - 缓存键：userId:file:fileId
     * - TTL：5分钟
     * - 条件：空结果不缓存
     */
    @Cacheable(
        value = "user_accessible_docs",
        key = "#userId + ':file:' + #fileId",
        unless = "#result.isEmpty()"
    )
    protected List<String> getDocIdsForFileScope(Long userId, Long fileId) {
        if (fileId == null) {
            throw new BusinessException("file scope必须提供fileId");
        }

        FileEntity file = fileMapper.selectById(fileId);
        if (file == null) {
            throw new BusinessException("文件不存在");
        }

        // 检查用户对该文件的访问权限
        if (!hasFilePermission(userId, file)) {
            throw new ForbiddenException("无权限访问该文件");
        }

        if (file.getRagflowDocId() == null) {
            throw new BusinessException("该文件尚未同步到知识库");
        }

        return List.of(file.getRagflowDocId());
    }

    /**
     * 递归获取文件夹ID列表（包括子文件夹）
     *
     * 修复：添加递归深度限制，防止栈溢出
     */
    private List<Long> getFolderIdsRecursive(Long folderId) {
        return getFolderIdsRecursiveWithDepth(folderId, 0);
    }

    /**
     * 递归获取文件夹ID列表（带深度限制）
     *
     * @param folderId 文件夹ID
     * @param depth 当前递归深度
     * @return 文件夹ID列表
     */
    private List<Long> getFolderIdsRecursiveWithDepth(Long folderId, int depth) {
        // 修复：添加递归深度限制（最多100层）
        final int MAX_DEPTH = 100;
        if (depth > MAX_DEPTH) {
            log.warn("文件夹递归深度超过限制({}): folderId={}, depth={}", MAX_DEPTH, folderId, depth);
            return new ArrayList<>();
        }

        List<Long> folderIds = new ArrayList<>();
        folderIds.add(folderId);

        // 查询子文件夹
        LambdaQueryWrapper<FolderEntity> query = new LambdaQueryWrapper<>();
        query.eq(FolderEntity::getParentId, folderId);
        List<FolderEntity> subFolders = folderMapper.selectList(query);

        for (FolderEntity subFolder : subFolders) {
            folderIds.addAll(getFolderIdsRecursiveWithDepth(subFolder.getId(), depth + 1));
        }

        return folderIds;
    }

    /**
     * 检查文件权限（v3.0隐私级别）
     */
    private boolean hasFilePermission(Long userId, FileEntity file) {
        UserEntity currentUser = userMapper.selectById(userId);
        if (currentUser == null) {
            return false;
        }

        switch (file.getPrivacyLevel()) {
            case "privacy":
                // 仅创建者可见
                return file.getUploadedBy().equals(userId);  // 使用uploadedBy而不是creatorId

            case "shared":
                // 同部门成员可见
                // 修复：添加null检查，避免NullPointerException
                if (file.getCreatorDeptId() == null || currentUser.getDepartmentId() == null) {
                    log.warn("文件或用户的部门信息不完整: fileId={}, userId={}", file.getId(), userId);
                    return false;  // 部门信息不完整，拒绝访问
                }
                return file.getCreatorDeptId().equals(currentUser.getDepartmentId());

            case "public":
                // 所有登录用户可见
                return true;

            default:
                return false;
        }
    }

    /**
     * 生成对话标题（取问题前30个字符）
     */
    private String generateDialogTitle(String query) {
        if (query.length() <= 30) {
            return query;
        }
        return query.substring(0, 30) + "...";
    }

    /**
     * 生成 RAGFlow Session 名称（v3.1新增）
     *
     * 格式：用户名-范围类型-时间戳
     * 示例：zhangsan-folder-20250107_143025
     *
     * @param userId 用户ID
     * @param dialog 对话实体
     * @return Session 名称
     */
    private String generateSessionName(Long userId, AiDialogEntity dialog) {
        UserEntity user = userMapper.selectById(userId);
        String username = user != null ? user.getUsername() : "User" + userId;
        String scopeType = dialog.getScopeType();
        String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"));
        return String.format("%s-%s-%s", username, scopeType, timestamp);
    }

    /**
     * 获取文件夹路径（直接返回path字段或递归构建）
     *
     * @param folderId 文件夹ID
     * @return 文件夹路径，例如：/根文件夹/子文件夹
     */
    private String getFolderPath(Long folderId) {
        if (folderId == null) {
            return "/";
        }

        FolderEntity folder = folderMapper.selectById(folderId);
        if (folder == null) {
            return "/";
        }

        // 直接返回path字段（如果存在）
        if (folder.getPath() != null && !folder.getPath().isEmpty()) {
            return folder.getPath();
        }

        // 否则递归构建路径
        if (folder.getParentId() == null || folder.getParentId() == 0) {
            return "/" + folder.getName();
        } else {
            String parentPath = getFolderPath(folder.getParentId());
            return parentPath + "/" + folder.getName();
        }
    }
}




